/*
Copyright 2023 The Karmada Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package kubernetes

import (
	"context"
	"testing"

	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes/fake"
)

func TestCommandInitOption_getKarmadaAPIServerIP(t *testing.T) {
	tests := []struct {
		name    string
		option  CommandInitOption
		nodes   []string
		labels  map[string]string
		wantErr bool
	}{
		{
			name: "KarmadaAPIServerAdvertiseAddress is not empty",
			option: CommandInitOption{
				KubeClientSet:                    fake.NewSimpleClientset(),
				KarmadaAPIServerAdvertiseAddress: "127.0.0.1",
			},
			nodes:   []string{"node1"},
			labels:  map[string]string{},
			wantErr: false,
		},
		{
			name: "three nodes but they are not master",
			option: CommandInitOption{
				KubeClientSet: fake.NewSimpleClientset(),
			},
			nodes:   []string{"node1", "node2", "node3"},
			labels:  map[string]string{},
			wantErr: false,
		},
		{
			name: "three master nodes",
			option: CommandInitOption{
				KubeClientSet: fake.NewSimpleClientset(),
			},
			nodes:   []string{"node1", "node2", "node3"},
			labels:  map[string]string{"node-role.kubernetes.io/control-plane": ""},
			wantErr: false,
		},
		{
			name: "no nodes",
			option: CommandInitOption{
				KubeClientSet: fake.NewSimpleClientset(),
			},
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			for _, v := range tt.nodes {
				_, err := tt.option.KubeClientSet.CoreV1().Nodes().Create(context.Background(), &corev1.Node{
					ObjectMeta: metav1.ObjectMeta{
						Name:   v,
						Labels: tt.labels,
					},
					Status: corev1.NodeStatus{
						Addresses: []corev1.NodeAddress{
							{Address: "127.0.0.1"},
						},
					},
				}, metav1.CreateOptions{})
				if err != nil {
					t.Errorf("create node error: %v", err)
				}
			}
			if err := tt.option.getKarmadaAPIServerIP(); (err != nil) != tt.wantErr {
				t.Errorf("CommandInitOption.getKarmadaAPIServerIP() = %v, want error:%v", err, tt.wantErr)
			}
		})
	}
}

func Test_nodeStatus(t *testing.T) {
	tests := []struct {
		name           string
		nodeConditions []corev1.NodeCondition
		isHealth       bool
	}{
		{
			name: "node is ready",
			nodeConditions: []corev1.NodeCondition{
				{Type: corev1.NodeReady, Status: corev1.ConditionTrue},
			},
			isHealth: true,
		},
		{
			name: "node is unready",
			nodeConditions: []corev1.NodeCondition{
				{Type: corev1.NodeReady, Status: corev1.ConditionFalse},
			},
			isHealth: false,
		},
		{
			name: "node's memory pressure is true",
			nodeConditions: []corev1.NodeCondition{
				{Type: corev1.NodeMemoryPressure, Status: corev1.ConditionTrue},
			},
			isHealth: false,
		},
		{
			name: "node's memory pressure is false",
			nodeConditions: []corev1.NodeCondition{
				{Type: corev1.NodeMemoryPressure, Status: corev1.ConditionFalse},
			},
			isHealth: true,
		},
		{
			name: "node's disk pressure is true",
			nodeConditions: []corev1.NodeCondition{
				{Type: corev1.NodeDiskPressure, Status: corev1.ConditionTrue},
			},
			isHealth: false,
		},
		{
			name: "node's disk pressure is false",
			nodeConditions: []corev1.NodeCondition{
				{Type: corev1.NodeDiskPressure, Status: corev1.ConditionFalse},
			},
			isHealth: true,
		},
		{
			name: "node's network unavailable is false",
			nodeConditions: []corev1.NodeCondition{
				{Type: corev1.NodeNetworkUnavailable, Status: corev1.ConditionFalse},
			},
			isHealth: true,
		},
		{
			name: "node's network unavailable is true",
			nodeConditions: []corev1.NodeCondition{
				{Type: corev1.NodeNetworkUnavailable, Status: corev1.ConditionTrue},
			},
			isHealth: false,
		},
		{
			name: "node's pid pressure is false",
			nodeConditions: []corev1.NodeCondition{
				{Type: corev1.NodePIDPressure, Status: corev1.ConditionFalse},
			},
			isHealth: true,
		},
		{
			name: "node's pid pressure is true",
			nodeConditions: []corev1.NodeCondition{
				{Type: corev1.NodePIDPressure, Status: corev1.ConditionTrue},
			},
			isHealth: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := nodeStatus(tt.nodeConditions); got != tt.isHealth {
				t.Errorf("nodeStatus() = %v, want %v", got, tt.isHealth)
			}
		})
	}
}

func TestCommandInitOption_AddNodeSelectorLabels(t *testing.T) {
	tests := []struct {
		name    string
		option  CommandInitOption
		status  corev1.ConditionStatus
		spec    corev1.NodeSpec
		wantErr bool
	}{
		{
			name: "there is healthy node",
			option: CommandInitOption{
				KubeClientSet: fake.NewSimpleClientset(),
			},
			status:  corev1.ConditionTrue,
			spec:    corev1.NodeSpec{},
			wantErr: false,
		},
		{
			name: "there is unhealthy node",
			option: CommandInitOption{
				KubeClientSet: fake.NewSimpleClientset(),
			},
			status:  corev1.ConditionFalse,
			spec:    corev1.NodeSpec{},
			wantErr: true,
		},
		{
			name: "there is taint node",
			option: CommandInitOption{
				KubeClientSet: fake.NewSimpleClientset(),
			},
			status: corev1.ConditionTrue,
			spec: corev1.NodeSpec{
				Taints: []corev1.Taint{
					{
						Effect: corev1.TaintEffectNoSchedule,
					},
				},
			},
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, err := tt.option.KubeClientSet.CoreV1().Nodes().Create(context.Background(), &corev1.Node{
				ObjectMeta: metav1.ObjectMeta{Name: "test-node"},
				Status: corev1.NodeStatus{
					Conditions: []corev1.NodeCondition{
						{Type: corev1.NodeReady, Status: tt.status},
					},
				},
				Spec: tt.spec,
			}, metav1.CreateOptions{})
			if err != nil {
				t.Errorf("create node error: %v", err)
			}
			if err := tt.option.AddNodeSelectorLabels(); (err != nil) != tt.wantErr {
				t.Errorf("CommandInitOption.AddNodeSelectorLabels() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}

func TestCommandInitOption_isNodeExist(t *testing.T) {
	tests := []struct {
		name     string
		option   CommandInitOption
		nodeName string
		labels   map[string]string
		exists   bool
	}{
		{
			name: "there is matched node",
			option: CommandInitOption{
				KubeClientSet: fake.NewSimpleClientset(),
			},
			nodeName: "node1",
			labels:   map[string]string{"foo": "bar"},
			exists:   true,
		},
		{
			name: "there is no matched node",
			option: CommandInitOption{
				KubeClientSet: fake.NewSimpleClientset(),
			},
			nodeName: "node2",
			labels:   map[string]string{"bar": "foo"},
			exists:   false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, err := tt.option.KubeClientSet.CoreV1().Nodes().Create(context.Background(), &corev1.Node{
				ObjectMeta: metav1.ObjectMeta{Name: tt.nodeName, Labels: tt.labels},
			}, metav1.CreateOptions{})
			if err != nil {
				t.Errorf("create node error: %v", err)
			}
			if got := tt.option.isNodeExist("foo=bar"); got != tt.exists {
				t.Errorf("CommandInitOption.isNodeExist() = %v, want %v", got, tt.exists)
			}
		})
	}
}
